=x= 🌵 建立 Specification Manager - Content Page 後台頁面。
📌 從 Yachts 頁面的 Specification 分頁內容裡可以觀察到 :
🧠 下半區塊 - 新增遊艇共用的特殊部位標題及列表。
🧠 左上區塊 - 型號下拉選單 + Layout & Deck Plan Image 組圖上傳。
🧠 右上區塊 - 遊艇部位標題下拉選單 + 部位細項細節添加。
🌵 型號下拉選單用來決定是哪個型號的資料。
🌵 細節標題下拉選單資料來源為上方下半區塊的表格。
🧠 細項規格分別對應遊艇型號及遊艇部位標題的 ID,並設定串聯刪除。
🌵 Layout & Deck Plan Image 組圖為圖檔檔名 JSON 資料存在 Yachts 資料表內。
🌵 遊艇部位標題因為是共用所以獨立一張表。
🧠 型號下拉選單設定直接使用精靈設定並將值設為 id,頁面設計參考如下
<h6>Yacht Model :</h6>
<asp:DropDownList ID="DListModel" runat="server" DataSourceID="SqlDataSource1" DataTextField="yachtModel" DataValueField="id" AutoPostBack="True" Width="100%" Font-Bold="True" class="btn btn-outline-primary dropdown-toggle" OnSelectedIndexChanged="DListModel_SelectedIndexChanged"></asp:DropDownList>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:TayanaYachtConnectionString %>" SelectCommand="SELECT [yachtModel], [id] FROM [Yachts]"></asp:SqlDataSource>
<hr />
<h6>Layout & Deck Plan Image :</h6>
<h6><span class="badge badge-pill badge-warning text-dark">* The maximum upload size at once is 10MB !</span></h6>
<div class="input-group my-3">
<asp:FileUpload ID="imageUpload" runat="server" class="btn btn-outline-primary btn-block" AllowMultiple="True" />
<asp:Button ID="UploadImgBtn" runat="server" Text="Upload" class="btn btn-primary" OnClick="UploadImgBtn_Click"/>
</div>
<hr />
<h6>Group Image List :</h6>
<asp:RadioButtonList ID="RadioButtonListImg" runat="server" class="my-3 mx-auto" AutoPostBack="True" OnSelectedIndexChanged="RadioButtonListImg_SelectedIndexChanged"></asp:RadioButtonList>
<asp:Button ID="DelImageBtn" runat="server" Text="Delete Image" type="button" class="btn btn-danger btn-sm" OnClientClick="return confirm('Are you sure you want to delete?')" Visible="False" OnClick="DelImageBtn_Click"/>
🧠 部位標題下拉選單設定直接使用精靈設定並將值設為 id,頁面設計參考如下
<h6>Detail Title :</h6>
<asp:DropDownList ID="DListDetailTitle" runat="server" DataSourceID="SqlDataSource2" DataTextField="detailTitleSort" DataValueField="id" AutoPostBack="True" Width="100%" Font-Bold="True" class="btn btn-outline-primary dropdown-toggle" OnSelectedIndexChanged="DListDetailTitle_SelectedIndexChanged"></asp:DropDownList>
<asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:TayanaYachtConnectionString %>" SelectCommand="SELECT [detailTitleSort], [id] FROM [DetailTitleSort]"></asp:SqlDataSource>
<hr />
<h6>Add Detail :</h6>
<asp:TextBox ID="TboxDetail" runat="server" type="text" class="form-control" placeholder="Enter detail text" TextMode="MultiLine" Height="100px"></asp:TextBox>
<asp:Button ID="BtnAddDetail" runat="server" Text="Add Detail" class="btn btn-outline-primary btn-block mt-3" OnClick="BtnAddDetail_Click"/>
<hr />
<h6>Detail List :</h6>
<asp:RadioButtonList ID="RadioButtonListDetail" runat="server" class="my-3 mx-auto" AutoPostBack="True" RepeatDirection="Vertical" OnSelectedIndexChanged="RadioButtonListD_SelectedIndexChanged" Width="100%"></asp:RadioButtonList>
<asp:Button ID="BtnDelDetail" runat="server" Text="Delete Detail" type="button" class="btn btn-danger btn-sm" OnClientClick="return confirm('Are you sure you want to delete?')" Visible="False" OnClick="BtnDelDetail_Click"/>
🧠 結合共用標題輸入功能與新增按鈕的頁面設計參考如下
<h6>Add New Title :</h6>
<div class="input-group mb-3">
<asp:TextBox ID="TBoxAddNewTitle" runat="server" type="text" class="form-control" placeholder="Enter new title" ></asp:TextBox>
<div class="input-group-append">
<asp:Button ID="BtnAddNewTitle" runat="server" Text="Add" class="btn btn-outline-primary btn-block" OnClick="BtnAddNewTitle_Click" />
</div>
</div>
🧠 共用標題的 GridView 表格頁面設計參考如下
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="id" DataSourceID="SqlDataSource3" BackColor="White" BorderColor="#CCCCCC" BorderStyle="None" BorderWidth="1px" CellPadding="3" Width="100%" OnRowDeleted="DeltedTitle" OnRowUpdated="UpdatedTitle">
<Columns>
<asp:CommandField ButtonType="Button" CancelText="Cancel" DeleteText="Delete" EditText="Edit" HeaderText="Edit" InsertText="Insert" NewText="New" SelectText="Select" ShowEditButton="True" ControlStyle-CssClass='btn btn-primary btn-block' ControlStyle-BorderColor="#66CCFF" ControlStyle-BorderStyle="Solid" ControlStyle-BorderWidth="1px" ControlStyle-ForeColor="White" >
<ControlStyle BorderColor="#66CCFF" BorderWidth="1px" BorderStyle="Solid" CssClass="btn btn-primary btn-block" ForeColor="White"></ControlStyle>
</asp:CommandField>
<asp:BoundField DataField="id" HeaderText="ID Number" InsertVisible="False" ReadOnly="True" SortExpression="id" >
<ItemStyle HorizontalAlign="Center" />
</asp:BoundField>
<asp:BoundField DataField="detailTitleSort" HeaderText="Detail Title" SortExpression="detailTitleSort" />
<asp:BoundField DataField="initDate" HeaderText="Creation Date" SortExpression="initDate" ReadOnly="True" InsertVisible="False" />
<asp:TemplateField HeaderText="Delete" ShowHeader="False">
<ItemTemplate>
<asp:LinkButton ID="BtnDeleteTitle" runat="server" CommandName="Delete" Text="Delete" OnClientClick="return confirm('Are you sure you want to delete?')" CausesValidation="False"></asp:LinkButton>
</ItemTemplate>
<ControlStyle BorderColor="#66CCFF" BorderStyle="Solid" BorderWidth="1px" CssClass="btn btn-danger btn-block" ForeColor="White" />
</asp:TemplateField>
</Columns>
<FooterStyle BackColor="White" ForeColor="#000066" />
<HeaderStyle BackColor="#006699" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="White" ForeColor="#000066" HorizontalAlign="Left" />
<RowStyle ForeColor="#000066" />
<SelectedRowStyle BackColor="#669999" Font-Bold="True" ForeColor="White" />
<SortedAscendingCellStyle BackColor="#F1F1F1" />
<SortedAscendingHeaderStyle BackColor="#007DBB" />
<SortedDescendingCellStyle BackColor="#CAC9C9" />
<SortedDescendingHeaderStyle BackColor="#00547E" />
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource3" runat="server" ConnectionString="<%$ ConnectionStrings:TayanaYachtConnectionString %>" SelectCommand="SELECT * FROM [DetailTitleSort]" DeleteCommand="DELETE FROM [DetailTitleSort] WHERE [id] = @id" UpdateCommand="UPDATE [DetailTitleSort] SET [detailTitleSort] = @detailTitleSort WHERE [id] = @id">
<DeleteParameters>
<asp:Parameter Name="id" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="detailTitleSort" Type="String" />
<asp:Parameter Name="id" Type="Int32" />
</UpdateParameters>
</asp:SqlDataSource>
//宣告 List<T> 方便用 Add 依序添加資料
private List<ImagePath> savePathList = new List<ImagePath>();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) {
DListModel.DataBind(); //先取得型號預設選取值
DListDetailTitle.DataBind(); //先取得細節標題預設選取值
loadImageList(); //取得 Layout 組圖
loadDetailList(); //取得標題細節
}
}
#region Group Image List
private void loadImageList()
{
//依型號取得組圖圖片資料
string selectModel_id = DListModel.SelectedValue;
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
string sqlLoad = "SELECT layoutDeckPlanImgPathJSON FROM Yachts WHERE id = @selectModel_id";
SqlCommand command = new SqlCommand(sqlLoad, connection);
command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.Read()) {
//將特殊符號解碼
string loadJson = HttpUtility.HtmlDecode(reader["layoutDeckPlanImgPathJSON"].ToString());
//反序列化JSON格式
savePathList = JsonConvert.DeserializeObject<List<ImagePath>>(loadJson);
}
connection.Close();
//渲染圖片選項
if (savePathList?.Count > 0) {
foreach (var item in savePathList) {
ListItem listItem = new ListItem($"<img src='/Tayanahtml/upload/Images/{item.SavePath}' alt='thumbnail' class='img-thumbnail' width='250px'/>", item.SavePath);
RadioButtonListImg.Items.Add(listItem);
}
}
DelImageBtn.Visible = false; //刪除鈕有選擇圖片時才顯示
}
protected void UploadImgBtn_Click(object sender, EventArgs e)
{
//有選擇檔案才執行
if (imageUpload.HasFile) {
//取得上傳檔案大小 (限制 10MB)
int fileSize = imageUpload.PostedFile.ContentLength;
if (fileSize < 1024 * 1000 * 10) {
//先讀取資料庫原有資料
loadImageList();
string savePath = Server.MapPath("~/Tayanahtml/upload/Images/");
//添加圖檔資料
foreach (HttpPostedFile postedFile in imageUpload.PostedFiles) {
//儲存圖片檔案及圖片名稱
//檢查專案資料夾內有無同名檔案,有同名就加流水號
DirectoryInfo directoryInfo = new DirectoryInfo(savePath);
string fileName = postedFile.FileName;
string[] fileNameArr = fileName.Split('.');
int count = 0;
foreach (var fileItem in directoryInfo.GetFiles()) {
if (fileItem.Name.Contains(fileNameArr[0])) {
count++;
}
}
fileName = fileNameArr[0] + $"({count + 1})." + fileNameArr[1];
postedFile.SaveAs(savePath + "temp" + fileName);
savePathList.Add(new ImagePath { SavePath = fileName });
//壓縮圖檔
var image = NetVips.Image.NewFromFile(savePath + "temp" + fileName);
if (image.Width > 672 * 2) {
var newImg = image.Resize(0.5);
while (newImg.Width > 672 * 2) {
newImg = newImg.Resize(0.5);
}
newImg.WriteToFile(savePath + fileName);
}
else {
postedFile.SaveAs(savePath + fileName);
}
File.Delete(savePath + "temp" + fileName);
}
//依遊艇型號更新資料
string selectModel_id = DListModel.SelectedValue;
//將 List<T> 資料轉為 JSON 格式字串
string savePathJsonStr = JsonConvert.SerializeObject(savePathList);
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
string sql = "UPDATE Yachts SET layoutDeckPlanImgPathJSON = @layoutDeckPlanImgPathJSON WHERE id = @selectModel_id";
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@layoutDeckPlanImgPathJSON", savePathJsonStr);
command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
//渲染畫面
RadioButtonListImg.Items.Clear();
loadImageList();
}
else {
Response.Write("<script>alert('*The maximum upload size is 10MB!');</script>");
}
}
}
// Layout 圖片 JSON 資料
public class ImagePath
{
public string SavePath { get; set; }
}
protected void RadioButtonListImg_SelectedIndexChanged(object sender, EventArgs e)
{
DelImageBtn.Visible = true;
}
protected void DelImageBtn_Click(object sender, EventArgs e)
{
//依選取項目刪除 List<T> 資料
loadImageList(); //先取得 List<T> 資料
string selImageStr = RadioButtonListImg.SelectedValue;
string savePath = Server.MapPath("~/Tayanahtml/upload/Images/");
File.Delete(savePath + selImageStr);
for (int i = 0; i < savePathList.Count; i++) {
if (savePathList[i].SavePath.Equals(selImageStr)) {
savePathList.RemoveAt(i);
}
}
//將 List<T> 資料轉為 JSON 格式字串
string savePathJsonStr = JsonConvert.SerializeObject(savePathList);
string selectModel_id = DListModel.SelectedValue;
//依選取型號更新圖檔資料
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
string sql = "UPDATE Yachts SET layoutDeckPlanImgPathJSON = @layoutDeckPlanImgPathJSON WHERE id = @selectModel_id";
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@layoutDeckPlanImgPathJSON", savePathJsonStr);
command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
//渲染畫面
RadioButtonListImg.Items.Clear();
loadImageList();
}
#endregion
#region
及#endregion
,因為前面相簿建立好後就可以整組沿用。private void loadDetailList()
{
//取得 Model 代表 id
string selectModel_id = DListModel.SelectedValue;
//取得 Title 代表 id
string selectTitle_id = DListDetailTitle.SelectedValue;
//依遊艇型號 id 及標題 id 取得 Detail
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
string sql = "SELECT detail FROM Specification WHERE yachtModel_ID = @selectModel_id AND detailTitleSort_ID = @selectTitle_id";
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
command.Parameters.AddWithValue("@selectTitle_id", selectTitle_id);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read()) {
string detail = reader["detail"].ToString();
//將轉成字元實體的編碼轉回 HTML 標籤語法渲染
ListItem listItem = new ListItem(HttpUtility.HtmlDecode(detail), detail);
RadioButtonListDetail.Items.Add(listItem);
}
connection.Close();
BtnDelDetail.Visible = false; //刪除鈕有選擇項目時才顯示
}
🌵 讀取資料條件設為遊艇型號 ID + 部位標題 ID。
🌵 這裡需要進行 HtmlDecode 是因為細項資料包含 <br>
換行標籤。
protected void BtnAddNewTitle_Click(object sender, EventArgs e)
{
//取得輸入標題字串
string newTitleStr = TBoxAddNewTitle.Text;
//1.連線資料庫
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
//2.sql語法
string sql = "INSERT INTO DetailTitleSort (detailTitleSort) VALUES(@newTitleStr)";
//3.創建command物件
SqlCommand command = new SqlCommand(sql, connection);
//4.參數化
command.Parameters.AddWithValue("@newTitleStr", newTitleStr);
//5.資料庫連線開啟
connection.Open();
//6.執行sql (新增刪除修改)
command.ExecuteNonQuery(); //單純執行無回傳值
//7.資料庫關閉
connection.Close();
//畫面渲染
GridView1.DataBind();
DListDetailTitle.DataBind();
//下拉選單改為選取最新項
DListDetailTitle.SelectedIndex = DListDetailTitle.Items.Count - 1;
//清空輸入欄位
TBoxAddNewTitle.Text = "";
}
protected void DeltedTitle(object sender, GridViewDeletedEventArgs e)
{
//刷新下拉選單
DListDetailTitle.DataBind();
//刷新細節項目
RadioButtonListDetail.Items.Clear();
RadioButtonListDetail.DataBind();
loadDetailList();
}
protected void UpdatedTitle(object sender, GridViewUpdatedEventArgs e)
{
//刷新下拉選單
DListDetailTitle.DataBind();
//刷新細節項目
RadioButtonListDetail.Items.Clear();
RadioButtonListDetail.DataBind();
loadDetailList();
}
protected void BtnAddDetail_Click(object sender, EventArgs e)
{
//取得新增 Detail
string newDetailStr = TboxDetail.Text;
//將換行跳脫字元改成 HTML 換行標籤
newDetailStr = newDetailStr.Replace("\r\n", "<br>");
//依取得下拉選項的值 (id) 存入 Detail 資料
string selectModel_id = DListModel.SelectedValue;
string selectTitle_id = DListDetailTitle.SelectedValue;
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
string sql = "INSERT INTO Specification (yachtModel_ID, detailTitleSort_ID, detail) VALUES (@selectModel_id, @selectTitle_id, @detail)";
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
command.Parameters.AddWithValue("@selectTitle_id", selectTitle_id);
//特殊符號要轉成字元實體才能正常存進資料庫
command.Parameters.AddWithValue("@detail", HttpUtility.HtmlEncode(newDetailStr));
connection.Open();
command.ExecuteNonQuery();
connection.Close();
//將改成 HTML 換行標籤資料加入選項渲染畫面
ListItem listItem = new ListItem(newDetailStr, newDetailStr);
RadioButtonListDetail.Items.Add(listItem);
TboxDetail.Text = "";
}
🌵 這裡需要將換行的跳脫字元r\n
換成<br>
換行標籤。
🌵 想看跳脫字元,可以安裝 Notepad++,將文字貼到記事本用 Notepad++開啟。
protected void BtnDelDetail_Click(object sender, EventArgs e)
{
//依選取資料刪除 Detail 資料
string selectModel_id = DListModel.SelectedValue;
string selectTitle_id = DListDetailTitle.SelectedValue;
string selectDetailStr = RadioButtonListDetail.SelectedValue;
SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
string sql = "DELETE FROM Specification WHERE yachtModel_ID = @selectModel_id AND detailTitleSort_ID = @selectTitle_id AND detail = @selectDetailStr";
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@selectModel_id", selectModel_id);
command.Parameters.AddWithValue("@selectTitle_id", selectTitle_id);
command.Parameters.AddWithValue("@selectDetailStr", selectDetailStr);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
//渲染畫面
RadioButtonListDetail.Items.Clear();
loadDetailList();
}
protected void RadioButtonListD_SelectedIndexChanged(object sender, EventArgs e)
{
//顯示細節項目刪除按鈕
BtnDelDetail.Visible = true;
}
protected void DListDetailTitle_SelectedIndexChanged(object sender, EventArgs e)
{
//刷新細節選項
RadioButtonListDetail.Items.Clear();
loadDetailList();
}
protected void DListModel_SelectedIndexChanged(object sender, EventArgs e)
{
//刷新全部選項
RadioButtonListImg.Items.Clear();
RadioButtonListDetail.Items.Clear();
loadImageList();
loadDetailList();
}
📢 本日後台頁面,因為主要內容為細項規格,但由於細項規格是由兩個下拉選單所決定,在取值跟畫面的更新時,要特別注意是否刷新,另外就是多行文字記得要將跳脫字元,轉換為 HTML 標籤,這樣才能正常呈現換行的效果。